/*-------------------------------------------------------------------------
 *
 * fe-secure-gssapi.c
 *   前端（客户端）对 GSSAPI 的加密支持
 *
 * Portions Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *  src/interfaces/libpq/fe-secure-gssapi.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include "fe-gssapi-common.h"
#include "libpq-fe.h"
#include "libpq-int.h"
#include "port/pg_bswap.h"


/*
 * 需要支持加密，以及相互认证和
 * 防篡改措施。
 */
#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \
	GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG

/*
 * 使用 GSSAPI 处理数据的加密/解密。
 *
 * 在网络上的加密数据流中，我们将数据
 * 分成数据包，每个数据包以一个 uint32 大小的长度
 * 字（网络字节序）开始，接着是该长度的加密数据
 * 紧随其后。解密将得到未使用加密时
 * 显示的相同数据流。
 *
 * 加密数据通常比未加密的同样数据
 * 大，因此我们使用固定大小的缓冲区来处理
 * 加密/解密，这些缓冲区通常会比 PQComm 的缓冲区要大，以最小化我们需要
 * 创建多个数据包（因此对单个
 * 读/写调用进行多次 recv/send 调用）的次数。
 *
 * 注意：客户端和服务器必须就最大数据包大小达成一致，
 * 因为我们必须将整个数据包一次性传递给 GSSAPI，
 * 我们不希望另一方发送任意巨大的数据包，因为我们将必须为它们分配内存然后再传递给 GSSAPI。
 *
 * 因此，这两个 #define 实际上是协议
 * 规格的一部分，不能被修改。
 */
#define PQ_GSS_SEND_BUFFER_SIZE 16384
#define PQ_GSS_RECV_BUFFER_SIZE 16384

/*
 * 我们需要这些状态变量 per-connection。为了使这个文件中的函数
 * 看起来与 be-secure-gssapi.c 中的函数大致相同，设置
 * 这些宏。
 */
#define PqGSSSendBuffer (fc_conn->gss_SendBuffer)
#define PqGSSSendLength (fc_conn->gss_SendLength)
#define PqGSSSendNext (fc_conn->gss_SendNext)
#define PqGSSSendConsumed (fc_conn->gss_SendConsumed)
#define PqGSSRecvBuffer (fc_conn->gss_RecvBuffer)
#define PqGSSRecvLength (fc_conn->gss_RecvLength)
#define PqGSSResultBuffer (fc_conn->gss_ResultBuffer)
#define PqGSSResultLength (fc_conn->gss_ResultLength)
#define PqGSSResultNext (fc_conn->gss_ResultNext)
#define PqGSSMaxPktSize (fc_conn->gss_MaxPktSize)


/*
 * 尝试将 len 字节的数据从 ptr 写入一个 GSSAPI 加密的连接。
 *
 * 该连接必须已经设置好 GSSAPI 加密（即，GSSAPI
 * 传输协商已完成）。
 *
 * 成功时，返回消耗的数据字节数（可能少于
 * len）。失败时，返回 -1 并适当地设置 errno。如果 errno
 * 表示不可重试的错误，则向 conn->errorMessage 添加一条消息。
 * 对于可重试的错误，调用者应在
 * 套接字准备好后再次调用（传递相同或更多
 * 数据）。
 */
ssize_t pg_GSS_write(PGconn *fc_conn, const void *fc_ptr, size_t fc_len)
{
	OM_uint32	fc_major,
				fc_minor;
	gss_buffer_desc fc_input,
				fc_output = GSS_C_EMPTY_BUFFER;
	ssize_t		fc_ret = -1;
	size_t		fc_bytes_to_encrypt;
	size_t		fc_bytes_encrypted;
	gss_ctx_id_t fc_gctx = fc_conn->gctx;

	/*
	 * 当我们遇到可重试的失败时，必须不告诉调用者我们已
	 * 成功传输了所有内容，否则它将不会重试。为了
	 * 简便起见，我们声称在成功传输所有 "len" 字节之前，
	 * 我们没有传输任何内容。在调用之间，当前输入数据中已经
	 * 被加密并放入 PqGSSSendBuffer（并可能已传输）的数量
	 * 存储在 PqGSSSendConsumed 中。在重试时，调用者
	 * *必须* 重新发送该数据，因此如果它提供的 len 小于该值，说明出现了问题。
	 *
	 * 注意：在我们成功发送任何加密数据包后报告部分写入完成似乎很有吸引力。
	 * 然而，这会给调用者带来问题；特别是，pqPutMsgEnd 的启发式发送只完整的 8K 块与这样的黑客方法相互作用很糟糕。
	 * 允许调用者提前丢弃数据通常不会节省太多，因此不要冒这个险。
	 */
	if (fc_len < PqGSSSendConsumed)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 "GSSAPI caller failed to retransmit all data needing to be retried\n");
		errno = EINVAL;
		return -1;
	}

	/* 抵消我们已经加密的任何源数据。 */
	fc_bytes_to_encrypt = fc_len - PqGSSSendConsumed;
	fc_bytes_encrypted = PqGSSSendConsumed;

	/*
	 * 循环遍历加密数据并将其发送出去，直到所有数据发送完毕或
	 * pqsecure_raw_write() 报告错误（这可能意味着套接字
	 * 是非阻塞的，请求的 send() 将会阻塞，或发生了某种
	 * 实际错误）。
	 */
	while (fc_bytes_to_encrypt || PqGSSSendLength)
	{
		int			fc_conf_state = 0;
		uint32		fc_netlen;

		/*
		 * 检查加密输出缓冲区中是否有需要发送的数据（可能是
		 * 从之前的调用中留下的），如果有，尝试发送它。如果我们
		 * 不能发送，将这一事实返回给调用者。
		 */
		if (PqGSSSendLength)
		{
			ssize_t		fc_retval;
			ssize_t		fc_amount = PqGSSSendLength - PqGSSSendNext;

			fc_retval = pqsecure_raw_write(fc_conn, PqGSSSendBuffer + PqGSSSendNext, fc_amount);
			if (fc_retval <= 0)
				return fc_retval;

			/*
			 * 检查这是否是部分写入，如果是，向前移动到
			 * 我们的缓冲区，并重新尝试。
			 */
			if (fc_retval < fc_amount)
			{
				PqGSSSendNext += fc_retval;
				continue;
			}

			/* 我们已经成功发送了缓冲区中的所有数据。 */
			PqGSSSendLength = PqGSSSendNext = 0;
		}

		/*
		 * 检查是否还有字节待加密。如果没有，我们就完成了。
		 */
		if (!fc_bytes_to_encrypt)
			break;

		/*
		 * 检查我们被要求发送多少数据，如果太多，
		 * 那么我们必须循环并可能被多次调用以发送
		 * 所有数据。
		 */
		if (fc_bytes_to_encrypt > PqGSSMaxPktSize)
			fc_input.length = PqGSSMaxPktSize;
		else
			fc_input.length = fc_bytes_to_encrypt;

		fc_input.value = (char *) fc_ptr + fc_bytes_encrypted;

		fc_output.value = NULL;
		fc_output.length = 0;

		/*
		 * 创建下一个加密数据包。这里的任何失败都视为
		 * 严重失败，因此即使已发送一些数据，我们也返回 -1。
		 */
		fc_major = gss_wrap(&fc_minor, fc_gctx, 1, GSS_C_QOP_DEFAULT,
						 &fc_input, &fc_conf_state, &fc_output);
		if (fc_major != GSS_S_COMPLETE)
		{
			pg_GSS_error(libpq_gettext("GSSAPI wrap error"), fc_conn, fc_major, fc_minor);
			errno = EIO;		/* 由于缺乏更好的想法 */
			goto cleanup;
		}

		if (fc_conf_state == 0)
		{
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("outgoing GSSAPI message would not use confidentiality\n"));
			errno = EIO;		/* 由于缺乏更好的想法 */
			goto cleanup;
		}

		if (fc_output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("client tried to send oversize GSSAPI packet (%zu > %zu)\n"),
							  (size_t) fc_output.length,
							  PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32));
			errno = EIO;		/* 由于缺乏更好的想法 */
			goto cleanup;
		}

		fc_bytes_encrypted += fc_input.length;
		fc_bytes_to_encrypt -= fc_input.length;
		PqGSSSendConsumed += fc_input.length;

		/* 4 个网络序字节的长度，然后是有效负载 */
		fc_netlen = pg_hton32(fc_output.length);
		memcpy(PqGSSSendBuffer + PqGSSSendLength, &fc_netlen, sizeof(uint32));
		PqGSSSendLength += sizeof(uint32);

		memcpy(PqGSSSendBuffer + PqGSSSendLength, fc_output.value, fc_output.length);
		PqGSSSendLength += fc_output.length;

		/* 释放 GSSAPI 分配的缓冲区存储 */
		gss_release_buffer(&fc_minor, &fc_output);
	}

	/* 如果我们到这里，所有的计数器应该匹配。 */
	Assert(fc_len == PqGSSSendConsumed);
	Assert(fc_len == fc_bytes_encrypted);

	/* 我们报告所有数据已发送，因此重置 PqGSSSendConsumed。 */
	PqGSSSendConsumed = 0;

	fc_ret = fc_bytes_encrypted;

cleanup:
	/* 释放 GSSAPI 缓冲区存储，如果我们还没有释放 */
	if (fc_output.value != NULL)
		gss_release_buffer(&fc_minor, &fc_output);
	return fc_ret;
}

/*
 * 从 GSSAPI 加密连接读取最多 len 字节的数据到 ptr。
 *
 * 连接必须已设置为 GSSAPI 加密（即，GSSAPI
 * 传输协商已完成）。
 *
 * 返回读取的数据字节数，如果失败，则返回 -1
 * 并适当地设置 errno。如果 errno 指示不可重试
 * 的错误，则会将消息添加到 conn->errorMessage。对于可重试的错误，
 * 调用者应在套接字准备好后再次调用。
 */
ssize_t pg_GSS_read(PGconn *fc_conn, void *fc_ptr, size_t fc_len)
{
	OM_uint32	fc_major,
				fc_minor;
	gss_buffer_desc fc_input = GSS_C_EMPTY_BUFFER,
				fc_output = GSS_C_EMPTY_BUFFER;
	ssize_t		fc_ret;
	size_t		fc_bytes_returned = 0;
	gss_ctx_id_t fc_gctx = fc_conn->gctx;

	/*
	 * 这里的计划是读取一个传入的加密数据包到
	 * PqGSSRecvBuffer，将其解密到 PqGSSResultBuffer，然后将
	 * 数据分发给调用者。当当前输入数据包用完时，读取另一个。
	 */
	while (fc_bytes_returned < fc_len)
	{
		int			fc_conf_state = 0;

		/* 检查我们是否有可以立即返回的数据在我们的缓冲区中 */
		if (PqGSSResultNext < PqGSSResultLength)
		{
			size_t		fc_bytes_in_buffer = PqGSSResultLength - PqGSSResultNext;
			size_t		fc_bytes_to_copy = Min(fc_bytes_in_buffer, fc_len - fc_bytes_returned);

			/*
			 * 将数据从我们的结果缓冲区复制到调用者的缓冲区，
			 * 在我们最后填充他们的缓冲区的地方。
			 */
			memcpy((char *) fc_ptr + fc_bytes_returned, PqGSSResultBuffer + PqGSSResultNext, fc_bytes_to_copy);
			PqGSSResultNext += fc_bytes_to_copy;
			fc_bytes_returned += fc_bytes_to_copy;

			/*
			 * 此时，我们要么填满了调用者的缓冲区，要么
			 * 清空了我们的结果缓冲区。无论哪种情况，返回给调用者。
			 * 在第二种情况下，我们可以尝试读取另一个加密数据包，
			 * 但很有可能没有可用的。 （如果这种情况不成立，
			 * 我们选择了过小的最大数据包大小。）无论如何，
			 * 让调用者处理我们已经返回的数据都是没有害处的。
			 */
			break;
		}

		/* 结果缓冲区为空，因此重置缓冲指针 */
		PqGSSResultLength = PqGSSResultNext = 0;

		/*
		 * 因为我们选择在发出一些数据后立即返回
		 * ，此时 bytes_returned 必须为零。因此
		 * 下面的故障退出可以直接返回 -1，而不必担心
		 * 我们是否已经发出了一些数据。
		 */
		Assert(fc_bytes_returned == 0);

		/*
		 * 此时，我们的结果缓冲区为空，并且请求读取更多字节。
		 * 我们现在准备加载下一个数据包并
		 * 完全解密到我们的结果缓冲区中。
		 */

		/* 如果我们还没有收集长度，则收集长度 */
		if (PqGSSRecvLength < sizeof(uint32))
		{
			fc_ret = pqsecure_raw_read(fc_conn, PqGSSRecvBuffer + PqGSSRecvLength,
									sizeof(uint32) - PqGSSRecvLength);

			/* 如果 ret <= 0，pqsecure_raw_read 已经设置了正确的 errno */
			if (fc_ret <= 0)
				return fc_ret;

			PqGSSRecvLength += fc_ret;

			/* 如果我们仍然没有得到长度，返回给调用者 */
			if (PqGSSRecvLength < sizeof(uint32))
			{
				errno = EWOULDBLOCK;
				return -1;
			}
		}

		/* 解码数据包长度并检查是否为超长数据包 */
		fc_input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);

		if (fc_input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
							  (size_t) fc_input.length,
							  PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32));
			errno = EIO;		/* 由于缺乏更好的想法 */
			return -1;
		}

		/*
		 * 在此调用中尽可能多地读取数据包，
		 * 从我们上一次被调用时离开的地方开始读取。
		 */
		fc_ret = pqsecure_raw_read(fc_conn, PqGSSRecvBuffer + PqGSSRecvLength,
								fc_input.length - (PqGSSRecvLength - sizeof(uint32)));
		/* 如果 ret <= 0，pqsecure_raw_read 已经设置了正确的 errno */
		if (fc_ret <= 0)
			return fc_ret;

		PqGSSRecvLength += fc_ret;

		/* 如果我们还没有完整的数据包，返回给调用者 */
		if (PqGSSRecvLength - sizeof(uint32) < fc_input.length)
		{
			errno = EWOULDBLOCK;
			return -1;
		}

		/*
		 * 现在我们有了完整的数据包，可以执行解密并
		 * 重新填充我们的结果缓冲区，然后返回循环将数据返回给
		 * 调用者。请注意，下面的错误退出必须处理
		 * 释放 gss 输出缓冲区。
		 */
		fc_output.value = NULL;
		fc_output.length = 0;
		fc_input.value = PqGSSRecvBuffer + sizeof(uint32);

		fc_major = gss_unwrap(&fc_minor, fc_gctx, &fc_input, &fc_output, &fc_conf_state, NULL);
		if (fc_major != GSS_S_COMPLETE)
		{
			pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), fc_conn,
						 fc_major, fc_minor);
			fc_ret = -1;
			errno = EIO;		/* 由于缺乏更好的想法 */
			goto cleanup;
		}

		if (fc_conf_state == 0)
		{
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("incoming GSSAPI message did not use confidentiality\n"));
			fc_ret = -1;
			errno = EIO;		/* 由于缺乏更好的想法 */
			goto cleanup;
		}

		memcpy(PqGSSResultBuffer, fc_output.value, fc_output.length);
		PqGSSResultLength = fc_output.length;

		/* 我们的接收缓冲区现在为空，重置它 */
		PqGSSRecvLength = 0;

		/* 释放 GSSAPI 分配的缓冲区存储 */
		gss_release_buffer(&fc_minor, &fc_output);
	}

	fc_ret = fc_bytes_returned;

cleanup:
	/* 释放 GSSAPI 缓冲区存储，如果我们还没有释放 */
	if (fc_output.value != NULL)
		gss_release_buffer(&fc_minor, &fc_output);
	return fc_ret;
}

/*
 * 从 pqsecure_raw_read 读取的简单封装。
 *
 * 这接受与 pqsecure_raw_read 相同的参数，另外还有一个输出参数
 * 用于返回读取的字节数。这处理了是否会发生阻塞以及
 * 如果我们在连接上检测到 EOF。
 */
static PostgresPollingStatusType
fc_gss_read(PGconn *fc_conn, void *fc_recv_buffer, size_t fc_length, ssize_t *fc_ret)
{
	*fc_ret = pqsecure_raw_read(fc_conn, fc_recv_buffer, fc_length);
	if (*fc_ret < 0)
	{
		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
			return PGRES_POLLING_READING;
		else
			return PGRES_POLLING_FAILED;
	}

	/* 检查 EOF */
	if (*fc_ret == 0)
	{
		int			fc_result = pqReadReady(fc_conn);

		if (fc_result < 0)
			return PGRES_POLLING_FAILED;

		if (!fc_result)
			return PGRES_POLLING_READING;

		*fc_ret = pqsecure_raw_read(fc_conn, fc_recv_buffer, fc_length);
		if (*fc_ret < 0)
		{
			if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
				return PGRES_POLLING_READING;
			else
				return PGRES_POLLING_FAILED;
		}
		if (*fc_ret == 0)
			return PGRES_POLLING_FAILED;
	}

	return PGRES_POLLING_OK;
}

/*
 * 协商连接的 GSSAPI 传输。当完成时，返回
 * PGRES_POLLING_OK。当发生阻塞时，将根据情况返回
 * PGRES_POLLING_READING 或 PGRES_POLLING_WRITING，并且
 * 如果无法协商传输则返回 PGRES_POLLING_FAILED。
 */
PostgresPollingStatusType pqsecure_open_gss(PGconn *fc_conn)
{
	ssize_t		fc_ret;
	OM_uint32	fc_major,
				fc_minor;
	uint32		fc_netlen;
	PostgresPollingStatusType fc_result;
	gss_buffer_desc fc_input = GSS_C_EMPTY_BUFFER,
				fc_output = GSS_C_EMPTY_BUFFER;

	/*
	 * 如果这是该连接的首次调用，分配缓冲区并
	 * 初始化状态变量。通过分别 malloc 缓冲区，我们
	 * 确保它们在本文件的某些地方进行长度字访问时对齐。
	 */
	if (PqGSSSendBuffer == NULL)
	{
		PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE);
		PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
		PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
		if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer)
		{
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("out of memory\n"));
			return PGRES_POLLING_FAILED;
		}
		PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
		PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0;
	}

	/*
	 * 检查我们是否有来自先前调用的任何数据可以发送，如果有，则发送。
	 */
	if (PqGSSSendLength)
	{
		ssize_t		fc_amount = PqGSSSendLength - PqGSSSendNext;

		fc_ret = pqsecure_raw_write(fc_conn, PqGSSSendBuffer + PqGSSSendNext, fc_amount);
		if (fc_ret < 0)
		{
			if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
				return PGRES_POLLING_WRITING;
			else
				return PGRES_POLLING_FAILED;
		}

		if (fc_ret < fc_amount)
		{
			PqGSSSendNext += fc_ret;
			return PGRES_POLLING_WRITING;
		}

		PqGSSSendLength = PqGSSSendNext = 0;
	}

	/*
	 * 客户端先发送，并且发送会创建一个上下文，因此在第一次调用时
	 * 这将为 false，然后当我们再次被调用时我们
	 * 将检查是否有传入数据。
	 */
	if (fc_conn->gctx)
	{
		/* 处理我们可能有的任何传入数据 */

		/* 查看我们是否仍在尝试获取长度 */
		if (PqGSSRecvLength < sizeof(uint32))
		{
			/* 尝试先获取长度 */
			fc_result = fc_gss_read(fc_conn, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength, &fc_ret);
			if (fc_result != PGRES_POLLING_OK)
				return fc_result;

			PqGSSRecvLength += fc_ret;

			if (PqGSSRecvLength < sizeof(uint32))
				return PGRES_POLLING_READING;
		}

		/*
		 * 检查我们是否收到了一个错误数据包
		 *
		 * 这样做是安全的，因为我们不应该收到超过8192的数据包
		 * ，因此实际的长度字节，由于它们是以
		 * 网络字节顺序表示，对于任何真实数据包将以两个零字节开始。
		 */
		if (PqGSSRecvBuffer[0] == 'E')
		{
			/*
			 * 在启动期间的错误数据包中，我们没有获取长度，因此
			 * 仅仅读取我们可以放入缓冲区的尽可能多的数据（作为字符串，
			 * 所以在末尾留一个位置以放置NULL字节）并将其报告给调用者。
			 */
			fc_result = fc_gss_read(fc_conn, PqGSSRecvBuffer + PqGSSRecvLength, PQ_GSS_RECV_BUFFER_SIZE - PqGSSRecvLength - 1, &fc_ret);
			if (fc_result != PGRES_POLLING_OK)
				return fc_result;

			PqGSSRecvLength += fc_ret;

			Assert(PqGSSRecvLength < PQ_GSS_RECV_BUFFER_SIZE);
			PqGSSRecvBuffer[PqGSSRecvLength] = '\0';
			appendPQExpBuffer(&fc_conn->errorMessage, "%s\n", PqGSSRecvBuffer + 1);

			return PGRES_POLLING_FAILED;
		}

		/*
		 * 到这个时候我们应该有整个长度，所以提取它并
		 * 然后读取我们剩下的数据包的任何内容
		 */

		/* 获取长度并检查超长数据包 */
		fc_input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);
		if (fc_input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
							  (size_t) fc_input.length,
							  PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32));
			return PGRES_POLLING_FAILED;
		}

		/*
		 * 在此调用中尽可能多地读取数据包，
		 * 从我们上一次被调用时离开的地方开始读取。
		 */
		fc_result = fc_gss_read(fc_conn, PqGSSRecvBuffer + PqGSSRecvLength,
						  fc_input.length - (PqGSSRecvLength - sizeof(uint32)), &fc_ret);
		if (fc_result != PGRES_POLLING_OK)
			return fc_result;

		PqGSSRecvLength += fc_ret;

		/*
		 * 如果我们获得的内容少于剩余数据包，那么我们需要返回
		 * 并再次被调用。
		 */
		if (PqGSSRecvLength - sizeof(uint32) < fc_input.length)
			return PGRES_POLLING_READING;

		fc_input.value = PqGSSRecvBuffer + sizeof(uint32);
	}

	/* 加载服务名称（如果已经完成则无操作） */
	fc_ret = pg_GSS_load_servicename(fc_conn);
	if (fc_ret != STATUS_OK)
		return PGRES_POLLING_FAILED;

	/*
	 * 调用GSS初始化上下文，或者使用空输入，或者使用来自服务器的完整
	 * 数据包。
	 */
	fc_major = gss_init_sec_context(&fc_minor, fc_conn->gcred, &fc_conn->gctx,
								 fc_conn->gtarg_nam, GSS_C_NO_OID,
								 GSS_REQUIRED_FLAGS, 0, 0, &fc_input, NULL,
								 &fc_output, NULL, NULL);

	/* GSS初始化安全上下文使用整个数据包，因此清除它 */
	PqGSSRecvLength = 0;

	if (GSS_ERROR(fc_major))
	{
		pg_GSS_error(libpq_gettext("could not initiate GSSAPI security context"),
					fc_conn, fc_major, fc_minor);
		return PGRES_POLLING_FAILED;
	}

	if (fc_output.length == 0)
	{
		/*
		 * 我们完成了 - 好极了！  设置标志，告诉低级I/O例程
		 * 执行GSS包装/解包装。
		 */
		fc_conn->gssenc = true;

		/* 清理 */
		gss_release_cred(&fc_minor, &fc_conn->gcred);
		fc_conn->gcred = GSS_C_NO_CREDENTIAL;
		gss_release_buffer(&fc_minor, &fc_output);

		/*
		 * 确定在考虑长度后能适应我们缓冲区的最大数据包大小。 pg_GSS_write需要这个。
		 */
		fc_major = gss_wrap_size_limit(&fc_minor, fc_conn->gctx, 1, GSS_C_QOP_DEFAULT,
									PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32),
									&PqGSSMaxPktSize);

		if (GSS_ERROR(fc_major))
		{
			pg_GSS_error(libpq_gettext("GSSAPI size check error"), fc_conn,
						 fc_major, fc_minor);
			return PGRES_POLLING_FAILED;
		}

		return PGRES_POLLING_OK;
	}

	/* 必须有 output.length > 0 */
	if (fc_output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
	{
		pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
					 fc_conn, fc_major, fc_minor);
		gss_release_buffer(&fc_minor, &fc_output);
		return PGRES_POLLING_FAILED;
	}

	/* 将令牌排队进行写入 */
	fc_netlen = pg_hton32(fc_output.length);

	memcpy(PqGSSSendBuffer, (char *) &fc_netlen, sizeof(uint32));
	PqGSSSendLength += sizeof(uint32);

	memcpy(PqGSSSendBuffer + PqGSSSendLength, fc_output.value, fc_output.length);
	PqGSSSendLength += fc_output.length;

	/* 我们在这里不在乎PqGSSSendConsumed */

	/* 释放 GSSAPI 分配的缓冲区存储 */
	gss_release_buffer(&fc_minor, &fc_output);

	/* 请求再次调用以写入数据 */
	return PGRES_POLLING_WRITING;
}

/*
 * GSSAPI信息函数。
 */

/*
 * 返回GSSAPI上下文本身。
 */
void * PQgetgssctx(PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;

	return fc_conn->gctx;
}

/*
 * 如果正在使用GSSAPI加密，则返回true。
 */
int PQgssEncInUse(PGconn *fc_conn)
{
	if (!fc_conn || !fc_conn->gctx)
		return 0;

	return fc_conn->gssenc;
}
